package com.dai.rpc.netty.codec;

import com.dai.rpc.compress.Compress;
import com.dai.rpc.constant.CompressTypeEnum;
import com.dai.rpc.constant.MessageTypeEnum;
import com.dai.rpc.constant.MyRpcConstants;
import com.dai.rpc.constant.SerializationTypeEnum;
import com.dai.rpc.exceptions.MyRpcException;
import com.dai.rpc.message.RpcMessage;
import com.dai.rpc.message.RpcRequest;
import com.dai.rpc.message.RpcResponse;
import com.dai.rpc.serialize.Serializer;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;

import java.util.ServiceLoader;

/**
 * RPC框架解码器
 * LengthFieldBasedFrameDecoder内部解决TCP粘包、拆包问题
 */
public class MyRpcDecoder extends LengthFieldBasedFrameDecoder {

    /**
     * 1. 4B  magic code（魔法数）
     * 2. 1B version（版本）
     * 3. 4B full length（整个报文消息长度）
     * 4. 1B messageType（消息类型）
     * 5. 1B codec（序列化类型）
     * 6. 1B compress（压缩类型）
     * 7. 4B  requestId（请求的Id）
     * 8. body（object类型数据）
     */

    public MyRpcDecoder(){
        super(1024 * 1024 * 8, 5, 4, -9, 0);
    }

    /**
     *
     * @param maxFrameLength  最大帧长度。它决定可以接收的数据的最大长度。如果超过，数据将被丢弃,根据实际环境定义
     * @param lengthFieldOffset  数据长度字段开始的偏移量, magic code+version=长度为5
     * @param lengthFieldLength  消息长度的大小  full length（消息长度） 长度为4
     * @param lengthAdjustment 补偿值 lengthAdjustment+数据长度取值=长度字段之后剩下包的字节数(x + 16=7 so x = -9)
     * @param initialBytesToStrip 忽略的字节长度，如果要接收所有的header+body 则为0，如果只接收body 则为header的长度 ,我们这为0
     */
    public MyRpcDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment, int initialBytesToStrip) {
        super(maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip);
    }

    @Override
    protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {

        Object decode = super.decode(ctx, in);
        // 解码
        if(decode instanceof ByteBuf){
            ByteBuf frame = (ByteBuf)  decode;
            if(frame.readableBytes() < MyRpcConstants.TOTAL_LENGTH){
                throw new MyRpcException("消息长度不符，格式有误");
            }
            return decodeFrame(frame);
        }
        return decode;
    }

    private Object decodeFrame(ByteBuf frame) {

        // 1.检查魔法数
        checkMagicCode(frame);
        // 2.检查版本号是否在定义范围内
        checkVersion(frame);
        // 3.读取消息长度
        int fullLength = frame.readInt();
        // 4.读取消息类型
        byte messageType = frame.readByte();
        // 5.读取序列化类型
        byte codecType = frame.readByte();
        // 6.读取压缩类型
        byte compressType = frame.readByte();
        // 7.读取请求id
        int requestId = frame.readInt();
        // 8.读取具体数据
        int bodyLength = fullLength - MyRpcConstants.TOTAL_LENGTH;
        RpcMessage message = RpcMessage.builder().codec(codecType)
                .messageType(messageType)
                .compress(compressType)
                .requestId(requestId)
                .build();
        if(bodyLength > 0){
            byte[] bodyData = new byte[bodyLength];
            frame.readBytes(bodyData);
            // 发送方发过来时，先是序列化，再压缩
            // 接收方接收时，应该先解压缩再反序列化
            // 解压缩
            Compress compress  = loadCompress(compressType);
            bodyData = compress.decompress(bodyData);
            // 反序列化
            Serializer serializer = loadSerializer(codecType);
            // 根据消息类型把数据反序列化并设置到message中
            if(MessageTypeEnum.REQUEST.getCode() ==  messageType){
                RpcRequest rpcRequest = (RpcRequest) serializer.deserialize(bodyData, RpcRequest.class);
                message.setData(rpcRequest);
            }
            if(MessageTypeEnum.RESPONSE.getCode() ==  messageType){
                RpcResponse rpcResponse = (RpcResponse) serializer.deserialize(bodyData, RpcResponse.class);
                message.setData(rpcResponse);
            }
        }
        return message;
    }

    private Serializer loadSerializer(byte codecType) {
        // 根据spi技术找到需要加载的实现类
        String serializerName = SerializationTypeEnum.getName(codecType);
        ServiceLoader<Serializer> load = ServiceLoader.load(Serializer.class);
        for (Serializer serializer : load) {
            if(serializer.name().equals(serializerName)) return serializer;
        }
        throw new  MyRpcException("未找到对应的序列化类型");
    }

    private Compress loadCompress(byte compressType) {
        // 根据spi技术找到需要加载的实现类
        String compressName = CompressTypeEnum.getName(compressType);
        ServiceLoader<Compress> load = ServiceLoader.load(Compress.class);
        for (Compress compress : load) {
            if(compress.name().equals(compressName)) return compress;
        }
        throw new  MyRpcException("未找到对应的压缩类型");
    }

    private void checkVersion(ByteBuf frame) {
        byte version = frame.readByte();
        if(version != MyRpcConstants.RPC_VERSION){
            throw new MyRpcException("版本号不符，格式有误");
        }
    }

    private void checkMagicCode(ByteBuf frame) {
        // 检查魔法值，不对就抛出异常
        byte[] magics = new byte[MyRpcConstants.RPC_MAGIC_TABLE.length];
        frame.readBytes(magics);
        for(int i = 0;i < magics.length;i ++){
            if(magics[i] != MyRpcConstants.RPC_MAGIC_TABLE[i]){
                throw new MyRpcException("魔法值不符，格式有误");
            }
        }
    }

}
